#!/bin/env python
# -*- coding: utf-8 -*-

# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc.  All rights reserved.
# http://code.google.com/p/protobuf/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Note that varint.py is a heavily stripped down version of Google's own
# internal Python protobuf methods.
#
# You can find the original code here  -
# https://github.com/remoun/python-protobuf/blob/master/google/protobuf/internal/encoder.py

# And -
# https://github.com/remoun/python-protobuf/blob/master/google/protobuf/internal/decoder.py

"""
File Name: varint_util.py
Author: morre <morre@vip.qq.com>
Create Time: 2022/12/31 17:05:06
Brief:
"""

from __future__ import absolute_import


class NotEnoughDataExcption(Exception):
    pass


def _varint_decoder(mask, result_type):
    """Return an encoder for a basic varint value (does not include tag).

    Decoded values will be bitwise-anded with the given mask before being
    returned, e.g. to limit them to 32 bits.  The returned decoder does not
    take the usual "end" parameter -- the caller is expected to do bounds
    checking after the fact (often the caller can defer such checking until
    later). The decoder returns a (value, new_pos) pair.
    """

    def decode_varint(buffer, pos):
        result = 0
        shift = 0
        while 1:
            if pos > len(buffer) - 1:
                raise NotEnoughDataExcption("Not enough data to decode varint")
            b = buffer[pos]
            result |= ((b & 0x7f) << shift)
            pos += 1
            if not b & 0x80:
                result &= mask
                result = result_type(result)
                return (result, pos)
            shift += 7
            if shift >= 64:
                raise ValueError("Too many bytes when decoding varint.")

    return decode_varint


def _signed_varint_decoder(bits, result_type):
    """Like _VarintDecoder() but decodes signed values."""

    signbit = 1 << (bits - 1)
    mask = (1 << bits) - 1

    def decode_varint(buffer, pos):
        result = 0
        shift = 0
        while 1:
            if pos > len(buffer) - 1:
                raise NotEnoughDataExcption("Not enough data to decode varint")
            b = buffer[pos]
            result |= ((b & 0x7f) << shift)
            pos += 1
            if not b & 0x80:
                result &= mask
                result = (result ^ signbit) - signbit
                result = result_type(result)
                return (result, pos)
            shift += 7
            if shift >= 64:
                raise ValueError("Too many bytes when decoding varint.")
    return decode_varint

# We force 32-bit values to int and 64-bit values to long to make
# alternate implementations where the distinction is more significant
# (e.g. the C++ implementation) simpler.


decodeVarint = _varint_decoder((1 << 64) - 1, int)
decodeSignedVarint = _signed_varint_decoder(64, int)

# Use these versions for values which must be limited to 32 bits.
decodeVarint32 = _varint_decoder((1 << 32) - 1, int)
decodeSignedVarint32 = _signed_varint_decoder(32, int)


def varint_size(value):
    """Compute the size of a varint value."""
    if value <= 0x7f:
        return 1
    if value <= 0x3fff:
        return 2
    if value <= 0x1fffff:
        return 3
    if value <= 0xfffffff:
        return 4
    if value <= 0x7ffffffff:
        return 5
    if value <= 0x3ffffffffff:
        return 6
    if value <= 0x1ffffffffffff:
        return 7
    if value <= 0xffffffffffffff:
        return 8
    if value <= 0x7fffffffffffffff:
        return 9
    return 10


def signed_varint_size(value):
    """Compute the size of a signed varint value."""
    if value < 0:
        return 10
    if value <= 0x7f:
        return 1
    if value <= 0x3fff:
        return 2
    if value <= 0x1fffff:
        return 3
    if value <= 0xfffffff:
        return 4
    if value <= 0x7ffffffff:
        return 5
    if value <= 0x3ffffffffff:
        return 6
    if value <= 0x1ffffffffffff:
        return 7
    if value <= 0xffffffffffffff:
        return 8
    if value <= 0x7fffffffffffffff:
        return 9
    return 10


# ====================================================================
# Encoders!


def _varint_encoder():
    """Return an encoder for a basic varint value (does not include tag)."""

    def encode_varint(write, value, unused_deterministic=None):
        bits = value & 0x7f
        value >>= 7
        while value:
            write(bytes((0x80 | bits,)))
            bits = value & 0x7f
            value >>= 7
        return write(bytes((bits,)))

    return encode_varint


def _signed_varint_encoder():
    """Return an encoder for a basic signed varint value (does not include
    tag)."""

    def encode_signed_varint(write, value, unused_deterministic=None):
        if value < 0:
            value += (1 << 64)
        bits = value & 0x7f
        value >>= 7
        while value:
            write(bytes((0x80 | bits,)))
            bits = value & 0x7f
            value >>= 7
        return write(bytes((bits,)))

    return encode_signed_varint


encodeVarint = _varint_encoder()
encodeSignedVarint = _signed_varint_encoder()

# Little helper function that will return a byte-string representing the
# val encoded as a varint


def to_varint(val):
    temp = []
    encodeVarint(temp.append, val)
    return b"".join(temp)
